package com.box1312.fishpond;

import static org.fusesource.leveldbjni.JniDBFactory.factory;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map.Entry;

import org.fusesource.leveldbjni.JniDBFactory;
import org.iq80.leveldb.CompressionType;
import org.iq80.leveldb.DB;
import org.iq80.leveldb.DBIterator;
import org.iq80.leveldb.Options;
import org.iq80.leveldb.ReadOptions;
import org.iq80.leveldb.WriteBatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class LevelDB {

  private final Logger logger = LoggerFactory.getLogger(LevelDB.class);

  protected DB db;

  public LevelDB(String dbPath) {
    this(dbPath, 1024);
  }

  public LevelDB(String dbPath, int cacheSize) {
    Options options = new Options();
    options.createIfMissing(true);
    options.compressionType(CompressionType.SNAPPY);
    options.cacheSize(cacheSize * 1048576);

    File path = new File(dbPath);
    if (!path.exists()) {
      path.mkdirs();
    }
    try {
      db = factory.open(new File(dbPath), options);
    } catch (IOException e) {
      db = null;
      throw new RuntimeException(e);
    }
  }

  public static byte[] bytes(String str) {
    return JniDBFactory.bytes(str);
  }

  public static String asString(byte[] bytes) {
    return JniDBFactory.asString(bytes);
  }

  public void set(byte[] key, byte[] val) {
    db.put(key, val);
  }

  public void set(String key, String val) {
    set(JniDBFactory.bytes(key), JniDBFactory.bytes(val));
  }

  public void batchWrite(List<Entry<byte[], byte[]>> items) {
    WriteBatch batch = db.createWriteBatch();
    for (Entry<byte[], byte[]> item : items) {
      byte[] key = item.getKey();
      byte[] value = item.getValue();
      if (key != null && key.length > 0) {
        if (value != null && value.length > 0) {
          batch.put(key, value);
        } else {
          batch.delete(key);
        }
      }
    }
    try {
      db.write(batch);
    } finally {
      try {
        batch.close();
      } catch (IOException e) {
        logger.error("err on batch write", e);
      }
    }

  }

  public boolean exists(byte[] key) {
    ReadOptions option = new ReadOptions();
    option.fillCache(true).verifyChecksums(false);
    byte[] val = db.get(key, option);
    return val != null && val.length > 0;
  }

  public String get(String key) {
    byte[] val = get(JniDBFactory.bytes(key));
    return JniDBFactory.asString(val);
  }

  public byte[] get(byte[] key) {
    ReadOptions option = new ReadOptions();
    option.fillCache(true).verifyChecksums(true);
    return db.get(key, option);
  }

  public void del(String... keys) {
    WriteBatch batch = db.createWriteBatch();
    try {
      for (String key : keys) {
        batch.delete(JniDBFactory.bytes(key));
      }
      db.write(batch);
    } finally {
      try {
        batch.close();
      } catch (IOException e) {
        logger.error("err on del keys", e);
      }
    }
  }

  public long incrBy(String key, long increment) {
    long ret = 0;
    byte[] bKey = JniDBFactory.bytes(key);
    byte[] bytes = get(bKey);
    if (bytes != null && bytes.length == 8) {
      ret = Utils.INSTANCE.byteToLong(bytes);
    }
    if (increment != 0) {
      ret += increment;
      bytes = Utils.INSTANCE.longToBytes(ret);
      set(bKey, bytes);
    }
    return ret;
  }

  public DBIterator iterator() {
    ReadOptions option = new ReadOptions().fillCache(false).verifyChecksums(false);
    return db.iterator(option);
  }

  public void close() throws LevelStoreException {
    try {
      db.close();
    } catch (IOException e) {
      throw new LevelStoreException("err on close db", e);
    }
  }

}
